import axios from 'axios';
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';

import { REFRESH_TOKEN_CODE, REQUEST_SUCCESS_CODE } from '@/config';
import {
    errorByAxios,
    errorByAxiosResponse,
    errorByResult,
    localStg,
    transformRequestData
} from '@/utils';

import { handleRefreshToken } from './base';

// 定义重试请求队列
type RefreshRequestQueue = (config: AxiosRequestConfig) => void;

/** 自定义 axios 实例化对象 */
export default class CustomAxiosInstance {
    instance: AxiosInstance;
    isRefreshing: boolean;
    retryQueues: RefreshRequestQueue[];

    /**
     * 初始化请求实例
     * @param axiosConfig - axios配置
     */
    constructor(axiosConfig: AxiosRequestConfig) {
        this.instance = axios.create(axiosConfig);
        this.setInterceptor();
        this.isRefreshing = false;
        this.retryQueues = [];
    }

    /** 配置拦截器 */
    setInterceptor() {
        // 请求拦截器
        this.instance.interceptors.request.use(
            async (config) => {
                const handleConfig = { ...config };
                if (handleConfig.headers) {
                    // 数据转换
                    const contentType = handleConfig.headers[
                        'Content-Type'
                    ] as UnionKey.ContentType;
                    handleConfig.data = await transformRequestData(handleConfig.data, contentType);
                    // 设置token
                    let tokenInfo: Auth.Token | null = localStg.get('tokenInfo');
                    handleConfig.headers.Authorization = tokenInfo?.token || '';
                    handleConfig.headers['Refresh-Authorization'] = tokenInfo?.refreshToken || '';
                }
                return handleConfig;
            },
            (axiosError: AxiosError) => {
                return errorByAxios(axiosError);
            }
        );

        // 响应拦截器
        this.instance.interceptors.response.use(
            (async (response) => {
                const { status, config } = response;
                if (status === 200 || status < 300 || status === 304) {
                    const backend = response.data as Api.Result;
                    // 响应头包含 token 就说明后端返回了新的 token
                    if (response.headers.authorization) {
                        localStg.set('tokenInfo', {
                            token: response.headers['authorization'],
                            refreshToken: response.headers?.['refresh-authorization']
                        } as Auth.Token);
                        config.headers.Authorization = response.headers['authorization'];
                        config.headers['Refresh-Authorization'] =
                            response.headers?.['Refresh-Authorization'];
                    }
                    // 请求成功
                    if (backend.code === REQUEST_SUCCESS_CODE) {
                        return backend;
                    }
                    // token失效, 刷新token
                    if (REFRESH_TOKEN_CODE.includes(backend.code)) {
                        // 原始请求
                        const originRequest = new Promise((resolve) => {
                            this.retryQueues.push((refreshConfig: AxiosRequestConfig) => {
                                config.headers.Authorization =
                                    refreshConfig.headers?.['authorization'];
                                config.headers['Refresh-Authorization'] =
                                    response.headers?.['Refresh-Authorization'];
                                resolve(this.instance.request(config));
                            });
                        });

                        if (!this.isRefreshing) {
                            this.isRefreshing = true;
                            const refreshConfig = await handleRefreshToken(response.config);
                            if (refreshConfig) {
                                this.retryQueues.map((cb) => cb(refreshConfig));
                            }
                            this.retryQueues = [];
                            this.isRefreshing = false;
                        }
                        return originRequest;
                    }

                    return errorByResult(backend as Api.FailedResult);
                }
                return errorByAxiosResponse(response);
            }) as (response: AxiosResponse<any, any>) => Promise<AxiosResponse<any, any>>,
            (axiosError: AxiosError) => {
                return errorByAxios(axiosError);
            }
        );
    }
}
